package com.example.net1;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.Socket;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.SignedObject;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Calendar;
import java.util.GregorianCalendar;

import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SealedObject;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class Client {

	public PublicKey bankPublicKey;
	public static final String pathTocert = "./serverCA.crt";
	
	public Client() {		
	}
	
	public void encryptAndStoreBankInfo(String firstName, String lastName,
			long cardNumber, int cvvNumber,
			long expirationDate) {
		SecretKey key = generateKey();
		try {
			BankInformation bankInfo = new BankInformation(firstName, lastName, 
					cardNumber, cvvNumber, expirationDate);
			FileOutputStream fos = new FileOutputStream("bank_information");
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			EncryptedObject encryptedBankInformation = new EncryptedObject(
					BankInformation.encrypt(bankInfo, key), encryptKey(key, bankPublicKey));
			oos.writeObject(encryptedBankInformation);			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public SealedObject encryptKey(SecretKey key, PublicKey bankPublicKey) {
		SealedObject encryptedKey = null;
		try {
			Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
			cipher.init(Cipher.ENCRYPT_MODE, bankPublicKey);
			encryptedKey = new SealedObject(key, cipher);
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return encryptedKey;
	}

	public SecretKey decryptKey(SealedObject encryptedKey, PrivateKey bankPrivateKey) {
		SecretKey decryptedKey = null;
		try {
			decryptedKey = (SecretKey) encryptedKey.getObject(bankPrivateKey);
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return decryptedKey;
	}
	
	public SecretKey generateKey() {
		SecretKey key = null;
		try {
			KeyGenerator generator = KeyGenerator.getInstance("AES");
			generator.init(new SecureRandom());
			key = generator.generateKey();
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return key;
	}
	
	private Socket s;
	private OutputStream os;
	private InputStream is;
	private ObjectOutputStream oos;
	private ObjectInputStream ois;
	public void connectToCashDesk(String host, int port) {
		try {
			this.s = new Socket(host, port);
			os = s.getOutputStream();
			oos = new ObjectOutputStream(os);
			is = s.getInputStream();
			ois = new ObjectInputStream(is);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public void disconnect() {
		try {
			os.close();
			oos.close();
			is.close();
			ois.close();
			s.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public void handlePaymentDemand() {
		// Reception of the demand
		try {
			Receipt receipt = (Receipt)ois.readObject();
			printReceipt(receipt);
			// Ask for validation
			if (checkPaymentDemand(receipt)) {
				oos.writeObject(new Integer(1));
				// If demand accepted, then encrypt the bank info
				// with the nonce and the POS bank id
				SignedObject signedPaymentRequest = (SignedObject) ois.readObject();
				
				// Verify if the payment request has really been signed
				// by the cash desk
				Signature signature = 
						Signature.getInstance("SHA1withRSA");
				boolean verification = 
						signedPaymentRequest.verify(
								CryptoUtil.getRSApublicKey(pathTocert), signature);
				
				System.out.println("[Client] Payment request signature verification: " + verification);
				if (!verification) {
					disconnect();
					return;
				}
				
				PaymentRequest paymentRequest = (PaymentRequest) signedPaymentRequest.getObject();
				if (paymentRequest.amount != receipt.amount) { // check if cash desk did not change the amount to be payed
					disconnect();
					return;
				}
				Long sellerId = paymentRequest.sellerId;
				Long nonce = paymentRequest.nonce;
				System.out.println("[Client] < sellerId=" + sellerId);
				System.out.println("[Client] < nonce=" + nonce);
				
				ReceiptHtmlAdapter receiptHtmlAdapter = new ReceiptHtmlAdapter(receipt, nonce);
				receiptHtmlAdapter.write(new File("receipt_" + nonce + ".htm"));
				
				BankTicket bankTicket = new BankTicket(loadEncryptedBankInformation(), signedPaymentRequest);
				SecretKey key = generateKey();
				// Send AES encrypted bank ticket and RSA encrypted AES key
				EncryptedObject encryptedBankTicket = new EncryptedObject(
						BankInformation.encrypt(bankTicket, key), encryptKey(key, bankPublicKey));
				
				oos.writeObject(encryptedBankTicket);
			}
			else {
				// Demand rejected
				oos.writeObject(new Integer(0));
				disconnect();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SignatureException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		
	}
	
	private void printReceipt(Receipt receipt) {
		System.out.println("[Client] Receipt:");
		for (Item item : receipt.items)
			System.out.println("[Client]\t " + item.description + " (" + item.price + " USD)" + " x" + item.number + ": " + (item.price*item.number) + " USD");
		System.out.println("[Client]\t\tTotal: " + receipt.amount + " USD");
	}

	public boolean checkPaymentDemand(Receipt receipt) {
		return true;
	}
	
	public EncryptedObject loadEncryptedBankInformation() {
		EncryptedObject encryptedBankInformation = null;
		FileInputStream fis;
		try {
			fis = new FileInputStream("bank_information");
			ObjectInputStream ois = new ObjectInputStream(fis);
			encryptedBankInformation = (EncryptedObject) ois.readObject();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return encryptedBankInformation;
	}
	
	public static void main(String[] args) {
		Client client = new Client();
		KeyPairGenerator keyGen = null;
		try {
			keyGen = KeyPairGenerator.getInstance("RSA");
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	//	keyGen.initialize(2048); // at least 2048bits so that the modulus is greater than the data_to_be_encrypted size
        client.bankPublicKey = keyGen.genKeyPair().getPublic();
        client.bankPublicKey = CryptoUtil.getRSApublicKey(pathTocert);        
		client.encryptAndStoreBankInfo("Timothee", "Lenel", 
				Long.valueOf("1234567890123456"), 123, 0);
		client.connectToCashDesk("localhost", 55555); // suppose the POS server is listening on this port
		client.handlePaymentDemand();
		client.disconnect();
	}
}
